﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Reflection;
using System.IO;
using System.Xml.Linq;
using System.Web;
using Vit.Common.Collections;
using Vit.Common.Extensions;

namespace Vit.Common.Helpers
{
    public class ReflectionHelper
    {
        public static Assembly[] GetCurrentDomainAssemblies()
        {
            return AppDomain.CurrentDomain.GetAssemblies();
        }

        public static Assembly[] GetEntryAssemblies()
        {
            string path = null;
            if (HttpContext.Current != null)
            {
                path = HttpContext.Current.Server.MapPath("~/bin");
            }
            else
            {
                path = Assembly.GetEntryAssembly().Location;
            }
            return GetAssembliesInPath(path);
        }

        public static Assembly[] GetAssembliesInPath(string path)
        {
            DirectoryInfo dir = new DirectoryInfo(path);
            if (dir.Exists)
            {
                List<Assembly> asmList = new List<Assembly>();
                foreach (FileInfo fi in dir.GetFiles("*.dll"))
                {
                    asmList.Add(Assembly.LoadFrom(fi.FullName));
                }
                foreach (FileInfo fi in dir.GetFiles("*.exe"))
                {
                    asmList.Add(Assembly.LoadFrom(fi.FullName));
                }
                return asmList.ToArray();
            }
            return null;
        }

        public static List<Type> FindTypesInAssemblies(Assembly[] assemblies, Type baseType, bool exceptAbstractType)
        {
            List<Type> types = new List<Type>();
            foreach (Assembly asm in assemblies)
            {
                Type[] foundTypes = null;
                try
                {
                    Type[] asmTypes = asm.GetTypes();

                    if (baseType.IsInterface)
                    {
                        if (exceptAbstractType)
                            foundTypes = Array.FindAll(asmTypes, t => t.IsAbstract == false && IsInterfase(t, baseType));
                        else
                            foundTypes = Array.FindAll(asmTypes, t => IsInterfase(t, baseType));
                    }
                    else
                    {
                        if (exceptAbstractType)
                            foundTypes = Array.FindAll(asmTypes, t => t.IsAbstract == false && (t.IsSubclassOf(baseType) || t.Equals(baseType)));
                        else
                            foundTypes = Array.FindAll(asmTypes, t => t.IsSubclassOf(baseType) || t.Equals(baseType));
                    }
                }
                catch
                {
                    continue;
                }
                if (foundTypes != null)
                    types.AddRange(foundTypes);
            }
            return types;
        }

        public static List<Type> FindTypesInAppDomain(Type baseType, bool exceptAbstractType)
        {
            Assembly[] assemblies = GetCurrentDomainAssemblies(); 
            List<Type> types = FindTypesInAssemblies(assemblies, baseType, exceptAbstractType);
            if (types == null || types.Count <= 0)
            {
                assemblies = GetEntryAssemblies();
                types = FindTypesInAssemblies(assemblies, baseType, exceptAbstractType);
            }

            return types;
        }

        public static List<Type> FindTypesInAppDomainExceptStartWithAssmplyNames(Type baseType, bool exceptAbstractType, string[] exceptStartWithAssmplyNames)
        {
            if(exceptStartWithAssmplyNames == null || exceptStartWithAssmplyNames.Length <= 0)
                return FindTypesInAppDomain(baseType, exceptAbstractType);

            Assembly[] assemblies = Array.FindAll(GetCurrentDomainAssemblies(), o => Array.Exists(exceptStartWithAssmplyNames, s => o.FullName.StartsWith(s) == false));
            List<Type> types = FindTypesInAssemblies(assemblies, baseType, exceptAbstractType);
            if (types == null || types.Count <= 0)
            {
                assemblies = Array.FindAll(GetEntryAssemblies(), o => Array.Exists(exceptStartWithAssmplyNames, s => o.FullName.StartsWith(s) == false)); 
                types = FindTypesInAssemblies(assemblies, baseType, exceptAbstractType);
            }

            return types;
        }

        public static List<Type> FindTypesInAppDomainOnlyStartWithAssmplyNames(Type baseType, bool exceptAbstractType, string[] onlyStartWithAssmplyNames)
        {
            if (onlyStartWithAssmplyNames == null || onlyStartWithAssmplyNames.Length <= 0)
                return FindTypesInAppDomain(baseType, exceptAbstractType);

            Assembly[] assemblies = Array.FindAll(GetCurrentDomainAssemblies(), o => Array.Exists(onlyStartWithAssmplyNames, s => o.FullName.StartsWith(s)));
            List<Type> types = FindTypesInAssemblies(assemblies, baseType, exceptAbstractType);
            if (types == null || types.Count <= 0)
            {
                assemblies = Array.FindAll(GetEntryAssemblies(), o => Array.Exists(onlyStartWithAssmplyNames, s => o.FullName.StartsWith(s)));
                types = FindTypesInAssemblies(assemblies, baseType, exceptAbstractType);
            }

            return types;
        }

        public static Type FindTypeInAssemblies(Assembly[] assemblies, string typeName)
        {
            Type type = Type.GetType(typeName);
            if (type != null)
                return type;

            foreach (Assembly asm in assemblies)
            {
                try
                {
                    Type[] asmTypes = asm.GetTypes();

                    type = Array.Find(asmTypes, t => t.Name.Equals(typeName));
                    if (type != null)
                        return type;
                }
                catch
                {
                    continue;
                }
            }
            return null;
        }

        public static Type FindTypeInAppDomain(string typeName)
        {
            Type type = Type.GetType(typeName);
            if (type != null)
                return type;
            Assembly[] assemblies = GetEntryAssemblies();
            type = FindTypeInAssemblies(assemblies, typeName);
            if (type == null)
            {
                assemblies = GetCurrentDomainAssemblies();
                type = FindTypeInAssemblies(assemblies, typeName);
            }

            return type;
        }

        public static object CreateInstance(Type type, List<object> inputValues)
        {
            if (type.IsAbstract || type.IsInterface)
                return null;

            object obj = null;
            if (inputValues == null || inputValues.Count <= 0)
                obj = Activator.CreateInstance(type);
            else
                obj = Activator.CreateInstance(type, inputValues.ToArray());

            if (obj != null)
                return obj;

            ParameterModifier[] pms = null;
            Type[] paraTypes = null;
            object[] paras = BuildParameters(inputValues, null, null, out paraTypes, out pms);

            ConstructorInfo constructor = type.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.CreateInstance, null, paraTypes, pms);
            if (constructor != null)
                return constructor.Invoke(paras);
            else if (paras == null)
                return Activator.CreateInstance(type);
            return null;
        }

        public static object InvokeMethod(object obj, string methodName)
        {
            return InvokeMethod(obj, methodName, (List<object>)null, null, null);
        }

        public static object InvokeMethod(object obj, string methodName, List<object> inputValues)
        {
            return InvokeMethod(obj, methodName, inputValues, null, null);
        }

        public static object InvokeMethod(object obj, string methodName, List<object> inputValues, List<object> outputValues)
        {
            return InvokeMethod(obj, methodName, inputValues, null, outputValues);
        }

        public static object InvokeMethod(object obj, string methodName, List<object> inputValues, List<object> refValues, List<object> outputValues)
        {
            Type type = obj.GetType();
            ParameterModifier[] pms = null;
            Type[] paraTypes = null;
            object[] paras = BuildParameters(inputValues, refValues, outputValues, out paraTypes, out pms);

            //MethodInfo mi = type.GetMethod(methodName, BindingFlags.InvokeMethod, null, paraTypes, pms);

            //if (mi == null)
            //    return null;
            //object ret = mi.Invoke(obj, paras);

            object ret = type.InvokeMember(methodName, BindingFlags.InvokeMethod, null, obj, paras, pms, null, null);
            int idx = inputValues == null ? 0 : inputValues.Count;
            if (refValues != null)
            {
                for (int i = 0; i < refValues.Count; i++)
                {
                    refValues[i] = paras[idx];
                    idx++;
                }
            }
            if (outputValues != null)
            {
                for (int i = 0; i < outputValues.Count; i++)
                {
                    outputValues[i] = paras[idx];
                    idx++;
                }
            }
            return ret;
        }

        public static object InvokeMethod(object obj, string methodName, Dictionary<string, object> inputValues)
        {
            return InvokeMethod(obj, methodName, inputValues, null, null);
        }

        public static object InvokeMethod(object obj, string methodName, Dictionary<string, object> inputValues, Dictionary<string, object> outputValues)
        {
            return InvokeMethod(obj, methodName, inputValues, null, outputValues);
        }

        public static object InvokeMethod(object obj, string methodName, Dictionary<string, object> inputValues, Dictionary<string, object> refValues, Dictionary<string, object> outputValues)
        {
            if (inputValues == null)
                inputValues = new Dictionary<string, object>();
            if (refValues == null)
                refValues = new Dictionary<string, object>();
            if (outputValues == null)
                outputValues = new Dictionary<string, object>();
            Dictionary<string, object> paraList = null;
            MethodInfo mi = GetMethodInfo(obj.GetType(), methodName, inputValues, refValues, outputValues, out paraList);
            if (mi != null)
            {
                if (paraList == null || paraList.Count <= 0)
                {
                    return mi.Invoke(obj, null);
                }

                object[] paras = paraList.ToArray();
                object ret = mi.Invoke(obj, paras);

                int idx = 0;
                foreach (KeyValuePair<string, object> kvp in paraList)
                {
                    if (refValues.ContainsKey(kvp.Key))
                        refValues[kvp.Key] = paras[idx];
                    if (outputValues.ContainsKey(kvp.Key))
                        outputValues[kvp.Key] = paras[idx];
                    idx++;
                }
                return ret;
            }
            return null;
        }

        public static object GetPropertyValue(object obj, string propertyName)
        {
            Type type = obj.GetType();
            PropertyInfo pi = type.GetProperty(propertyName);
            if (pi != null)
                return pi.GetValue(obj, null);
            return null;
        }

        public static object GetPropertyValue(object obj, string propertyName, Type returnType)
        {
            Type type = obj.GetType();
            PropertyInfo pi = type.GetProperty(propertyName, returnType);
            if (pi != null)
                return pi.GetValue(obj, null);
            return null;
        }

        public static object GetPropertyValue(object obj, string propertyName, Type returnType, List<object> parameters, object[] index)
        {
            Type type = obj.GetType();
            ParameterModifier[] pms = null;
            Type[] paraTypes = null;
            object[] paras = BuildParameters(parameters, null, null, out paraTypes, out pms);
            PropertyInfo pi = type.GetProperty(propertyName, returnType, paraTypes, pms);
            if (pi != null)
                return pi.GetValue(obj, index);
            return null;
        }

        public static void SetPropertyValue(object obj, string propertyName, object val)
        {
            Type type = obj.GetType();
            PropertyInfo pi = type.GetProperty(propertyName);
            if (pi != null)
                pi.SetValue(obj, val, null);
        }

        public static void SetPropertyValue(object obj, string propertyName, Type returnType, object val)
        {
            Type type = obj.GetType();
            PropertyInfo pi = type.GetProperty(propertyName, returnType);
            if (pi != null)
                pi.SetValue(obj, val, null);
        }

        public static void SetPropertyValue(object obj, string propertyName, Type returnType, List<object> parameters, object[] index, object value)
        {
            Type type = obj.GetType();
            ParameterModifier[] pms = null;
            Type[] paraTypes = null;
            object[] paras = BuildParameters(parameters, null, null, out paraTypes, out pms);
            PropertyInfo pi = type.GetProperty(propertyName, returnType, paraTypes, pms);
            if (pi != null && pi.CanWrite)
                pi.SetValue(obj, value, index);
        }

        public static object GetFieldValue(object obj, string fieldName)
        {
            Type type = obj.GetType();
            FieldInfo fi = type.GetField(fieldName);
            if (fi != null)
                return fi.GetValue(obj);
            return null;
        }

        public static void SetFieldValue(object obj, string fieldName, object value)
        {
            Type type = obj.GetType();
            FieldInfo fi = type.GetField(fieldName);
            if (fi != null)
                fi.SetValue(obj, value);
        }

        public static bool IsInterfase(Type type, Type interfaceType)
        {
            if (type.GetInterface(interfaceType.Name) != null)
                return true;

            Type[] types = type.GetInterfaces();
            foreach (Type tp in types)
            {
                if (tp.Equals(interfaceType))
                    return true;
            }
            return false;
        }

        public static bool IsInterfase(Type type, string interfaceTypeName)
        {
            if (type.GetInterface(interfaceTypeName) != null)
                return true;

            Type[] types = type.GetInterfaces();
            foreach (Type tp in types)
            {
                if (tp.Name == interfaceTypeName || tp.FullName == interfaceTypeName)
                    return true;
            }
            return false;
        }

        public static bool IsFromGenericType(Type type, Type genericType)
        {
            return type.IsGenericType && genericType.MakeGenericType(type.GetGenericArguments()).IsAssignableFrom(type);
        }

        public static List<PropertyInfo> GetAttributedProperties(Type type, Type attributeType)
        {
            if (typeof(Attribute).IsAssignableFrom(attributeType) == false)
                return null;
            List<PropertyInfo> props = new List<PropertyInfo>();
            foreach (PropertyInfo prop in type.GetProperties())
            {
                Type propType = prop.PropertyType;
                object[] colAttrs = propType.GetCustomAttributes(attributeType, true);
                if (colAttrs != null && colAttrs.Length > 0)
                    props.Add(prop);
            }
            return props;
        }

        public static bool MemberHasAttribute(MemberInfo member, Type attributeType)
        {
            if (typeof(Attribute).IsAssignableFrom(attributeType) == false)
                return false;
            object[] attrs = member.GetCustomAttributes(attributeType, true);
            return attrs != null && attrs.Length > 0;
        }

        public static bool CheckMemberAttribute<T>(MemberInfo member, Func<T, bool> predict) where T : Attribute
        {
            object[] attrs = member.GetCustomAttributes(typeof(T), false);
            if (attrs == null || attrs.Length <= 0)
                attrs = member.GetCustomAttributes(typeof(T), true);
            if (attrs == null || attrs.Length <= 0)
                return false;
            return predict((T)attrs[0]);
        }

        internal static object[] BuildParameters(List<object> inputObjs, List<object> refObjs, List<object> outObjs, out Type[] paraTypes, out ParameterModifier[] paraModifiers)
        {
            paraModifiers = null;
            paraTypes = Type.EmptyTypes;
            int paraCount = 0;

            if (inputObjs != null)
                paraCount += inputObjs.Count;
            if (refObjs != null)
                paraCount += refObjs.Count;
            if (outObjs != null)
                paraCount += outObjs.Count;

            if (paraCount <= 0)
                return null;

            paraModifiers = new ParameterModifier[1];
            paraModifiers[0] = new ParameterModifier(paraCount);
            paraTypes = new Type[paraCount];
            object[] paras = new object[paraCount];

            int idx = 0;

            if (inputObjs != null)
            {
                foreach (object v in inputObjs)
                {
                    paraTypes[idx] = GetObjectType(v, false);
                    paraModifiers[0][idx] = false;
                    paras[idx] = v;
                    idx++;
                }
            }

            if (refObjs != null)
            {
                foreach (object v in refObjs)
                {
                    paraTypes[idx] = GetObjectType(v, true);
                    paras[idx] = v;
                    paraModifiers[0][idx] = true;
                    idx++;
                }
            }

            if (outObjs != null)
            {
                foreach (object v in outObjs)
                {
                    paraTypes[idx] = GetObjectType(v, true);
                    paras[idx] = v;
                    paraModifiers[0][idx] = true;
                    idx++;
                }
            }

            return paras;
        }

        internal static MethodInfo GetMethodInfo(Type type, string methodName, Dictionary<string, object> inputValues, Dictionary<string, object> refValues, Dictionary<string, object> outputValues, out Dictionary<string, object> paras)
        {
            paras = null;
            MethodInfo[] methods = type.GetMethods(BindingFlags.FlattenHierarchy | BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance);
            MethodInfo methodInfo = null;
            foreach (MethodInfo mi in methods)
            {
                if (string.Equals(methodName, mi.Name, StringComparison.OrdinalIgnoreCase) && !mi.IsGenericMethod)
                {
                    ParameterInfo[] miParas = mi.GetParameters();
                    if ((miParas == null || miParas.Length <= 0) && inputValues.Count <= 0 && refValues.Count <= 0 && outputValues.Count <= 0)
                    {
                        paras = null;
                        methodInfo = mi;
                        break;
                    }
                    else if (miParas.Length == inputValues.Count + refValues.Count + outputValues.Count)
                    {
                        paras = new Dictionary<string, object>();
                        bool isSame = true;
                        foreach (ParameterInfo para in miParas)
                        {
                            if (inputValues.ContainsKey(para.Name) && GetObjectType(inputValues[para.Name], false).Equals(para.ParameterType))
                            {
                                paras.Add(para.Name, inputValues[para.Name]);
                                continue;
                            }
                            else if (refValues.ContainsKey(para.Name) && GetObjectType(refValues[para.Name], true).Equals(para.ParameterType))
                            {
                                paras.Add(para.Name, refValues[para.Name]);
                                continue;
                            }
                            else if (outputValues.ContainsKey(para.Name) && GetObjectType(outputValues[para.Name], true).Equals(para.ParameterType))
                            {
                                paras.Add(para.Name, outputValues[para.Name]);
                                continue;
                            }
                            else
                            {
                                paras = null;
                                isSame = false;
                                break;
                            }
                        }
                        if (isSame)
                        {
                            methodInfo = mi;
                            break;
                        }
                    }
                    if (methodInfo != null)
                        break;
                }
            }
            return methodInfo;
        }

        private static Type GetObjectType(object obj, bool byRef)
        {
            if (byRef)
                return Type.GetType(obj == null ? "System.Object&" : obj.GetType().FullName + "&");
            else
                return obj == null ? typeof(object) : obj.GetType();
        }
    }
}
